/*
 * Copyright (C) 2008 The Android Open Source Project, Romain Guy
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ilarele.shopping.client.util;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.os.Handler;

public class CrossFadeDrawable extends Drawable {
	private static final int TRANSITION_STARTING = 0;
	private static final int TRANSITION_RUNNING = 1;
	private static final int TRANSITION_NONE = 2;

	private int mTransitionState = TRANSITION_NONE;

	private boolean mCrossFade;
	private boolean mReverse;
	private long mStartTimeMillis;
	private int mFrom;
	private int mTo;
	private int mDuration;
	private int mOriginalDuration;
	private int mAlpha;

	private Bitmap mStart;
	private Bitmap mEnd;

	private final Paint mStartPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
	private final Paint mEndPaint = new Paint(Paint.FILTER_BITMAP_FLAG);

	private float mStartX;
	private float mStartY;
	private float mEndX;
	private float mEndY;

	private final Handler mHandler;
	private final Runnable mInvalidater;

	public CrossFadeDrawable(Bitmap start, Bitmap end) {
		mStart = start;
		mEnd = end;
		mHandler = new Handler();
		mInvalidater = new Runnable() {
			public void run() {
				invalidateSelf();
			}
		};
	}

	/**
	 * Begin the second layer on top of the first layer.
	 * 
	 * @param durationMillis
	 *            The length of the transition in milliseconds
	 */
	public void startTransition(int durationMillis) {
		mFrom = 0;
		mTo = 255;
		mAlpha = 0;
		mOriginalDuration = mDuration = durationMillis;
		mReverse = false;
		mTransitionState = mStart != mEnd ? TRANSITION_STARTING
				: TRANSITION_NONE;
		invalidateSelf();
	}

	/**
	 * Show only the first layer.
	 */
	public void resetTransition() {
		mAlpha = 0;
		mTransitionState = TRANSITION_NONE;
		invalidateSelf();
	}

	/**
	 * Reverses the transition, picking up where the transition currently is. If
	 * the transition is not currently running, this will start the transition
	 * with the specified duration. If the transition is already running, the
	 * last known duration will be used.
	 * 
	 * @param duration
	 *            The duration to use if no transition is running.
	 */
	public void reverseTransition(int duration) {
		final long time = SystemClock.uptimeMillis();

		if (time - mStartTimeMillis > mOriginalDuration) {
			if (mAlpha == 0) {
				mFrom = 0;
				mTo = 255;
				mAlpha = 0;
				mReverse = false;
			} else {
				mFrom = 255;
				mTo = 0;
				mAlpha = 255;
				mReverse = true;
			}
			mDuration = mOriginalDuration = duration;
			mTransitionState = TRANSITION_STARTING;
			mHandler.post(mInvalidater);
			return;
		}

		mReverse = !mReverse;
		mFrom = mAlpha;
		mTo = mReverse ? 0 : 255;
		mDuration = (int) (mReverse ? time - mStartTimeMillis
				: mOriginalDuration - (time - mStartTimeMillis));
		mTransitionState = TRANSITION_STARTING;
	}

	@Override
	public void draw(Canvas canvas) {
		boolean done = true;

		switch (mTransitionState) {
		case TRANSITION_STARTING:
			mStartTimeMillis = SystemClock.uptimeMillis();
			done = false;
			mTransitionState = TRANSITION_RUNNING;
			break;
		case TRANSITION_RUNNING:
			if (mStartTimeMillis >= 0) {
				float normalized = (float) (SystemClock.uptimeMillis() - mStartTimeMillis)
						/ mDuration;

				done = normalized >= 1.0f;
				mAlpha = (int) (mFrom + (mTo - mFrom)
						* Math.min(normalized, 1.0f));

				if (done) {
					mTransitionState = TRANSITION_NONE;
					mHandler.post(mInvalidater);
				}
			}
			break;
		}

		final int alpha = mAlpha;
		final boolean crossFade = mCrossFade;

		Bitmap bitmap = mStart;
		Paint paint = mStartPaint;

		if (!crossFade || 255 - alpha > 0) {
			if (crossFade) {
				paint.setAlpha(255 - alpha);
			}
			canvas.drawBitmap(bitmap, mStartX, mStartY, paint);
			if (crossFade) {
				paint.setAlpha(0xFF);
			}
		}

		if (alpha > 0) {
			bitmap = mEnd;
			paint = mEndPaint;
			paint.setAlpha(alpha);
			canvas.drawBitmap(bitmap, mEndX, mEndY, paint);
			paint.setAlpha(0xFF);
		}

		if (!done) {
			mHandler.post(mInvalidater);
		}
	}

	Bitmap getStart() {
		return mStart;
	}

	void setStart(Bitmap start) {
		mStart = start;
		invalidateSelf();
	}

	Bitmap getEnd() {
		return mEnd;
	}

	public void setEnd(Bitmap end) {
		mEnd = end;
		invalidateSelf();
	}

	@Override
	public void setBounds(int left, int top, int right, int bottom) {
		super.setBounds(left, top, right, bottom);

		final int width = right - left;
		final int height = bottom - top;

		mStartX = (width - mStart.getWidth()) / 2.0f;
		mStartY = height - mStart.getHeight();

		mEndX = (width - mEnd.getWidth()) / 2.0f;
		mEndY = height - mEnd.getHeight();
	}

	@Override
	public int getIntrinsicWidth() {
		return Math.max(mStart.getWidth(), mEnd.getWidth());
	}

	@Override
	public int getIntrinsicHeight() {
		return Math.max(mStart.getHeight(), mEnd.getHeight());
	}

	@Override
	public int getMinimumWidth() {
		return Math.max(mStart.getWidth(), mEnd.getWidth());
	}

	@Override
	public int getMinimumHeight() {
		return Math.max(mStart.getHeight(), mEnd.getHeight());
	}

	@Override
	public void setDither(boolean dither) {
		mStartPaint.setDither(true);
		mEndPaint.setDither(true);
	}

	@Override
	public void setFilterBitmap(boolean filter) {
		mStartPaint.setFilterBitmap(true);
		mEndPaint.setFilterBitmap(true);
	}

	/**
	 * Ignored.
	 */
	public void setAlpha(int alpha) {
	}

	public void setColorFilter(ColorFilter cf) {
		mStartPaint.setColorFilter(cf);
		mEndPaint.setColorFilter(cf);
	}

	public int getOpacity() {
		return PixelFormat.TRANSLUCENT;
	}

	/**
	 * Enables or disables the cross fade of the drawables. When cross fade is
	 * disabled, the first drawable is always drawn opaque. With cross fade
	 * enabled, the first drawable is drawn with the opposite alpha of the
	 * second drawable.
	 * 
	 * @param enabled
	 *            True to enable cross fading, false otherwise.
	 */
	public void setCrossFadeEnabled(boolean enabled) {
		mCrossFade = enabled;
	}

	/**
	 * Indicates whether the cross fade is enabled for this transition.
	 * 
	 * @return True if cross fading is enabled, false otherwise.
	 */
	boolean isCrossFadeEnabled() {
		return mCrossFade;
	}
}
